/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// cl_tent.c -- client side temporary entities

#include "quakedef.h"

// prevent wild flickering when running fast; this also keeps random effects synced with time which means they won't flicker when paused
#define FLICKERTABLE_SIZE	65536

int flickertable[FLICKERTABLE_SIZE];

void CL_InitEffects (void)
{
	int i;

	for (i = 0; i < FLICKERTABLE_SIZE; i++)
		flickertable[i] = rand ();
}


int CL_TimedRandom (float offset, float rate)
{
	// keep random effects framerate-independent
	return flickertable[(int) (offset + cl.time * rate) & (FLICKERTABLE_SIZE - 1)];
}


struct beam_t
{
	int	entity;
	struct model_t	*model;
	float endtime;
	vec3_t start, end;

	struct beam_t *next;
};

beam_t		*free_beams = NULL;
beam_t		*active_beams = NULL;

sfx_t		*cl_sfx_wizhit = NULL;
sfx_t		*cl_sfx_kniggithit = NULL;
sfx_t		*cl_sfx_tink1 = NULL;
sfx_t		*cl_sfx_ric1 = NULL;
sfx_t		*cl_sfx_ric2 = NULL;
sfx_t		*cl_sfx_ric3 = NULL;
sfx_t		*cl_sfx_r_exp3 = NULL;

model_t		*cl_mod_bolt1 = NULL;
model_t		*cl_mod_bolt2 = NULL;
model_t		*cl_mod_bolt3 = NULL;
model_t		*cl_mod_beam = NULL;

void CL_NewTentsForNewMap (void)
{
	// preload sounds for tents
	cl_sfx_wizhit = S_PrecacheSound ("wizard/hit.wav");
	cl_sfx_kniggithit = S_PrecacheSound ("hknight/hit.wav");
	cl_sfx_tink1 = S_PrecacheSound ("weapons/tink1.wav");
	cl_sfx_ric1 = S_PrecacheSound ("weapons/ric1.wav");
	cl_sfx_ric2 = S_PrecacheSound ("weapons/ric2.wav");
	cl_sfx_ric3 = S_PrecacheSound ("weapons/ric3.wav");
	cl_sfx_r_exp3 = S_PrecacheSound ("weapons/r_exp3.wav");

	// preload models for tents
	cl_mod_bolt1 = Mod_ForName ("progs/bolt.mdl", FALSE);
	cl_mod_bolt2 = Mod_ForName ("progs/bolt2.mdl", FALSE);
	cl_mod_bolt3 = Mod_ForName ("progs/bolt3.mdl", FALSE);
	cl_mod_beam = Mod_ForName ("progs/beam.mdl", FALSE);

	// no beams initially
	free_beams = NULL;
	active_beams = NULL;
}


/*
=================
CL_ParseBeam
=================
*/
void CL_ParseBeam (model_t *m)
{
	int		ent;
	vec3_t	start, end;
	beam_t	*b;

	// must fully read the messages so that protocol state is consistent
	ent = MSG_ReadShort ();

	start[0] = MSG_ReadCoord ();
	start[1] = MSG_ReadCoord ();
	start[2] = MSG_ReadCoord ();

	end[0] = MSG_ReadCoord ();
	end[1] = MSG_ReadCoord ();
	end[2] = MSG_ReadCoord ();

	// if the model doesn't exist (progs/beam.mdl is not in id1) then don't add the beam
	if (!m) return;

	// override any beam with the same entity
	for (b = active_beams; b; b = b->next)
	{
		if (b->entity == ent)
		{
			b->entity = ent;
			b->model = m;
			b->endtime = cl.time + 0.2;
			Vector3Copy (b->start, start);
			Vector3Copy (b->end, end);
			return;
		}
	}

	// create a new free beam if we need to
	if (!free_beams)
	{
		free_beams = (beam_t *) mapPool->Alloc (sizeof (beam_t));
		free_beams->next = NULL;
	}

	// use the first free beam
	GetLinkFromFreeList (active_beams, b, free_beams);

	// and update it for this entity
	b->entity = ent;
	b->model = m;
	b->endtime = cl.time + 0.2;

	Vector3Copy (b->start, start);
	Vector3Copy (b->end, end);
}

/*
=================
CL_ParseTEnt
=================
*/
void CL_ParseTEnt (void)
{
	int		type;
	vec3_t	pos;
	dlight_t	*dl;
	int		rnd;
	int		colorStart, colorLength;

	// generate a unique key for each tent dlight so that they don't get incorrectly reused
	static int dlkey = -1;

	// and make it truly unique
	dlkey--;

	type = MSG_ReadByte ();

	switch (type)
	{
	case TE_WIZSPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		dl = CL_AllocDlight (dlkey, 250, 0.25f, DL_COLOR_GREEN);
		Vector3Copy (dl->origin, pos);
		dl->decay = 200;

		R_RunParticleEffect (pos, vec3_origin, 20, 30);
		S_StartSound (-1, 0, cl_sfx_wizhit, pos, 1, 1);
		break;

	case TE_KNIGGITSPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		dl = CL_AllocDlight (dlkey, 250, 0.25f, DL_COLOR_ORANGE);
		Vector3Copy (dl->origin, pos);
		dl->decay = 200;

		R_RunParticleEffect (pos, vec3_origin, 226, 20);
		S_StartSound (-1, 0, cl_sfx_kniggithit, pos, 1, 1);
		break;

	case TE_SPIKE:			// spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_RunParticleEffect (pos, vec3_origin, 0, 10);

		if (CL_TimedRandom (type, 36) % 5)
			S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = CL_TimedRandom (type, 36) & 3;

			if (rnd == 1)
				S_StartSound (-1, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-1, 0, cl_sfx_ric2, pos, 1, 1);
			else S_StartSound (-1, 0, cl_sfx_ric3, pos, 1, 1);
		}

		break;

	case TE_SUPERSPIKE:			// super spike hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_RunParticleEffect (pos, vec3_origin, 0, 20);

		if (CL_TimedRandom (type, 36) % 5)
			S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1);
		else
		{
			rnd = CL_TimedRandom (type, 36) & 3;

			if (rnd == 1)
				S_StartSound (-1, 0, cl_sfx_ric1, pos, 1, 1);
			else if (rnd == 2)
				S_StartSound (-1, 0, cl_sfx_ric2, pos, 1, 1);
			else S_StartSound (-1, 0, cl_sfx_ric3, pos, 1, 1);
		}

		break;

	case TE_GUNSHOT:			// bullet hitting wall
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_RunParticleEffect (pos, vec3_origin, 0, 20);
		break;

	case TE_EXPLOSION:			// rocket explosion
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_ParticleExplosion (pos);

		dl = CL_AllocDlight (dlkey, 350, 0.5f, DL_COLOR_ORANGE);
		Vector3Copy (dl->origin, pos);
		dl->decay = 300;

		S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;

	case TE_TAREXPLOSION:			// tarbaby explosion
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_BlobExplosion (pos);

		dl = CL_AllocDlight (dlkey, 350, 0.5f, DL_COLOR_PURPLE);
		Vector3Copy (dl->origin, pos);
		dl->decay = 300;

		S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;

	case TE_LIGHTNING1:				// lightning bolts
		CL_ParseBeam (cl_mod_bolt1);
		break;

	case TE_LIGHTNING2:				// lightning bolts
		CL_ParseBeam (cl_mod_bolt2);
		break;

	case TE_LIGHTNING3:				// lightning bolts
		CL_ParseBeam (cl_mod_bolt3);
		break;

	case TE_BEAM:				// grappling hook beam
		CL_ParseBeam (cl_mod_beam);
		break;

	case TE_LAVASPLASH:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_LavaSplash (pos);
		break;

	case TE_TELEPORT:
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();
		R_TeleportSplash (pos);
		break;

	case TE_EXPLOSION2:				// color mapped explosion
		pos[0] = MSG_ReadCoord ();
		pos[1] = MSG_ReadCoord ();
		pos[2] = MSG_ReadCoord ();

		colorStart = MSG_ReadByte ();
		colorLength = MSG_ReadByte ();

		R_ParticleExplosion2 (pos, colorStart, colorLength);

		dl = CL_AllocDlight (dlkey, 350, 0.5f, DL_COLOR_WHITE);
		Vector3Copy (dl->origin, pos);
		dl->decay = 300;

		S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1);
		break;

	default:
		Sys_Error ("CL_ParseTEnt: bad type");
	}
}


/*
=================
CL_UpdateTEnts
=================
*/
void CL_UpdateTEnts (void)
{
	beam_t		*b;
	vec3_t		dist, org;
	float		d;
	entity_t	*ent;
	float		yaw, pitch;
	float		forward;

	for (;;)
	{
		beam_t *kill = active_beams;

		if (kill && (!kill->model || kill->endtime < cl.time))
		{
			MoveLinkToFreeList (active_beams, kill, free_beams);
			continue;
		}

		break;
	}

	// update lightning
	for (b = active_beams; b; b = b->next)
	{
		for (;;)
		{
			beam_t *kill = b->next;

			if (kill && (!kill->model || kill->endtime < cl.time))
			{
				MoveLinkToFreeList (b->next, kill, free_beams);
				continue;
			}

			break;
		}

		// if coming from the player, update the start position
		if (b->entity == cl.viewentity)
			Vector3Copy (b->start, cl.entities[cl.viewentity]->origin);

		// calculate pitch and yaw
		VectorSubtract (b->end, b->start, dist);

		// can we use this same calculation for beams in Q2???
		if (dist[1] == 0 && dist[0] == 0)
		{
			yaw = 0;

			if (dist[2] > 0)
				pitch = 90;
			else pitch = 270;
		}
		else
		{
			yaw = (int) (atan2 (dist[1], dist[0]) * 180 / M_PI);
			forward = sqrt (dist[0] * dist[0] + dist[1] * dist[1]);
			pitch = (int) (atan2 (dist[2], forward) * 180 / M_PI);

			if (pitch < 0) pitch += 360;
			if (yaw < 0) yaw += 360;
		}

		// add new entities for the lightning
		Vector3Copy (org, b->start);
		d = Vector3Normalize (dist);

		while (d > 0)
		{
			// if we're out of visedicts we don't bother
			if (cl_numvisedicts >= MAX_EDICTS) break;

			// since the temp pool is wiped every frame we can just draw tents from it as requried
			if (!(ent = (entity_t *) qScratch->FastAlloc (sizeof (entity_t)))) break;

			// because scratch doesn't memset-zero
			memset (ent, 0, sizeof (entity_t));

			// nothing previous to lerp from
			ent->lerpflags = LERP_RESETALL;

			// clear other stuff
			Vector3Copy (ent->origin, org);
			CL_ClearRocketTrail (ent);

			ent->model = b->model;

			// defer adding the visedict to here because the ent doesn't have a model yet
			cl_visedicts[cl_numvisedicts] = ent;
			cl_numvisedicts++;

			// the roll angle should be framerate-independent, best guess is 36fps simulation
			ent->angles[0] = pitch;
			ent->angles[1] = yaw;
			ent->angles[2] = CL_TimedRandom (d, 36) % 360;

			Vector3Mad (org, dist, 30.0f, org);

			d -= 30.0f;
		}
	}
}


